﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using Pavilion.DesignerTool.ViewTools.ConnectableListBox;
using System.Threading.Tasks;
using System.Collections;
using System.Windows.Controls;

namespace Pavilion.DesignerTool
{
    public class ResourceData : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public string NodeLayoutPath { get { return GameEngine.NodeLayoutDirectory + ResourceName + @"\"; } }
        public string ResourceName { get { return ResourceItem.Name; } set { SetResourceName(value); } }
        public NotifiableCollection<AnimationData> AnimationDataCollection { get { return AnimationDataDictionary.ValueCollection; } }
        public NotifiableCollection<NodeScriptWrapper> NodeScriptWrapperCollection { get; private set; }
        public NotifiableCollection<ResourceType> ResourceTypeCollection { get; private set; }
        public ResourceItem ResourceItem { get; set; }

        private NotifiableDictionary<Animation, AnimationData> AnimationDataDictionary { get; set; }
        private AnimationXmlSerializer AnimationSerializer { get; set; }
        private ScriptXmlSerializer ScriptXmlSerializer { get; set; }

        private TrackedResourceBase trackedResourceBase;
        private List<AnimationData> originalAnimationDataList;
        private List<NodeScriptWrapper> originalNodeScriptWrapperList;
        private string originalName;

        public ResourceData(string resourceName)
        {
            this.ResourceItem = ResourceManager.Instance[resourceName];
            this.originalName = resourceName;

            trackedResourceBase = new TrackedResourceBase(this.ResourceItem);
            AnimationSerializer = new AnimationXmlSerializer();
            ScriptXmlSerializer = new ScriptXmlSerializer();
            ResourceTypeCollection = new NotifiableCollection<ResourceType>();

            LoadAnimationData();
            LoadNodeScriptWrappers();
        }

        private void LoadNodeScriptWrappers()
        {
            NodeScriptWrapperCollection = new NotifiableCollection<NodeScriptWrapper>();

            if (!ResourceItem.ContainResourceCollection<NodeScriptCollection>())
                ResourceItem.AddResourceCollection(new NodeScriptCollection());

            foreach (NodeScript nodeScript in ResourceItem.ObtainResourceCollection<NodeScriptCollection>())
                NodeScriptWrapperCollection.Add(new NodeScriptWrapper(nodeScript, this));

            ResourceItem.ObtainResourceCollection<NodeScriptCollection>().NodeScriptAdded += (nodeScript) =>
            {
                NodeScriptWrapperCollection.Add(new NodeScriptWrapper(nodeScript, this));
                NodeScriptTransfer.Add(ResourceName, nodeScript);
            };

            ResourceItem.ObtainResourceCollection<NodeScriptCollection>().NodeScriptRemoved += (nodeScript) =>
            {
                NodeScriptWrapper wrapperToRemove = NodeScriptWrapperCollection.First((x) => x.Name == nodeScript.Name);
                NodeScriptWrapperCollection.Remove(wrapperToRemove);
                NodeScriptTransfer.Remove(ResourceName, nodeScript);
            };

            ResourceTypeCollection.Add(new ResourceType("Scripts", NodeScriptWrapperCollection, "Add Script", () => AddNewScript()));

            originalNodeScriptWrapperList = new List<NodeScriptWrapper>(NodeScriptWrapperCollection);
        }

        private void LoadAnimationData()
        {
            AnimationDataDictionary = new NotifiableDictionary<Animation, AnimationData>();

            if (!ResourceItem.ContainResourceCollection<AnimationCollection>())
                ResourceItem.AddResourceCollection(new AnimationCollection());

            AnimationCollection animationCollection = ResourceItem.ObtainResourceCollection<AnimationCollection>();

            foreach (Animation animation in animationCollection)
                AnimationDataDictionary.Add(animation, new AnimationData(animation, this));

            originalAnimationDataList = new List<AnimationData>(AnimationDataCollection);

            animationCollection.AnimationAdded += (Animation animation) =>
            {
                AnimationData animationData = new AnimationData(animation, this);
                AnimationDataDictionary.Add(animation, animationData);
                AnimationTransfer.Add(ResourceName, animationData);
            };
            animationCollection.AnimationRemoved += (Animation animation) =>
            {
                AnimationDataDictionary.Remove(animation);
                AnimationTransfer.Remove(ResourceName, animation.Name);
            };

            ResourceTypeCollection.Add(new ResourceType("Animations", AnimationDataCollection, "Add Animation", () => AddNewAnimation()));
        }

        private CollectionDelta<AnimationData> collectionDelta;
        public void TransferDataReorder()
        {
            if (originalName != ResourceName)
                ResourceTransfer.Rename(originalName, ResourceName);

            collectionDelta = new CollectionDelta<AnimationData>(originalAnimationDataList, AnimationDataCollection);

            foreach (AnimationData newItem in collectionDelta.NewItems)
                AnimationTransfer.Add(ResourceName, newItem);

            foreach (AnimationData deletedItem in collectionDelta.DeletedItems)
                AnimationTransfer.Remove(ResourceName, deletedItem.Name);
        }

        public void TransferDataUpdate()
        {
            foreach (AnimationData animationData in collectionDelta.IdenticalItems)
                animationData.TransferData();

            trackedResourceBase.TransferData();
        }

        public void Save()
        {
            UpdateDirectories();

            SaveAnimationData();
            SaveNodeScripts();

            ResetSaveTrackingPoint();
        }

        private void SaveAnimationData()
        {
            foreach (AnimationData animationSaveData in AnimationDataDictionary.ValueCollection)
                animationSaveData.Save(ResourceItem.ResourcePath + @"Raw Data\", ResourceItem.ResourcePath);

            XDocument animationListDocument = AnimationSerializer.SerializeCollection(ResourceItem.ObtainResourceCollection<AnimationCollection>().AnimationList);

            animationListDocument.Save(ResourceItem.ResourcePath + "AnimationList.xml");
        }

        private void SaveNodeScripts()
        {
            RemoveDeletedScripts();

            foreach (NodeScriptWrapper nodeScriptWrapper in NodeScriptWrapperCollection)
                nodeScriptWrapper.Save();
        }

        private void RemoveDeletedScripts()
        {
            IEnumerable<NodeScriptWrapper> scriptsToRemove = new CollectionDelta<NodeScriptWrapper>(originalNodeScriptWrapperList, NodeScriptWrapperCollection).DeletedItems;

            foreach (NodeScriptWrapper removeNodeScript in scriptsToRemove)
                removeNodeScript.DeleteXmlScript();
        }

        private void UpdateDirectories()
        {
            if (originalName != ResourceName)
            {
                string originalResourceDirectory = GameEngine.AnimationsDirectory + originalName + @"\";

                if (Directory.Exists(originalResourceDirectory)) 
                    Directory.Move(originalResourceDirectory, ResourceItem.ResourcePath);

                string originalLayoutDirectory = GameEngine.NodeLayoutDirectory + originalName + @"\";

                if (Directory.Exists(originalLayoutDirectory))
                    Directory.Move(originalLayoutDirectory, NodeLayoutPath);
            }

            if (!Directory.Exists(ResourceItem.ResourcePath))
                Directory.CreateDirectory(ResourceItem.ResourcePath);
        }

        private void ResetSaveTrackingPoint()
        {
            originalAnimationDataList.Clear();
            originalAnimationDataList.AddRange(AnimationDataCollection);

            originalNodeScriptWrapperList.Clear();
            originalNodeScriptWrapperList.AddRange(NodeScriptWrapperCollection);

            this.originalName = ResourceName;

            trackedResourceBase = new TrackedResourceBase(this.ResourceItem);
        }

        public Animation AddNewAnimation()
        {
            return ResourceItem.ObtainResourceCollection<AnimationCollection>().AddNewAnimation();
        }

        private void AddNewScript()
        {
            string scriptName = GameEngine.GetUniqueName(NodeScriptWrapperCollection.Select(x => x.Name), "NodeScript");

            ResourceItem.ObtainResourceCollection<NodeScriptCollection>().AddNodeScript(new NodeScript(scriptName));
        }

        public AnimationData GetAnimationData(Animation animation)
        {
            if (animation == null)
                return null;

            return AnimationDataDictionary[animation];
        }

        public bool ContainsAnimationData(Animation animation)
        {
            return AnimationDataDictionary.ContainsKey(animation);
        }

        private void SetResourceName(string value)
        {
            if (ResourceItem.Name == value)
                return;

            string oldName = ResourceItem.Name;
            ResourceItem.Name = value;

            ResourceTransfer.Rename(oldName, value);

            OnPropertyChanged("ResourceName");
        }

        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class ResourceType
    {
        public string Name { get; private set; }
        public IEnumerable Collection { get; private set; }
        public ContextMenu ContextMenu { get; private set; }

        public ResourceType(string name, IEnumerable collection, string addNewItemName, Action addNewItemAction)
        {
            this.Name = name;
            this.Collection = collection;

            ContextMenu = new ContextMenu();
            MenuItem menuItem = new MenuItem();
            menuItem.Header = addNewItemName;
            menuItem.Click += (sender, e) => addNewItemAction();

            ContextMenu.Items.Add(menuItem);
        }

        void menuItem_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            throw new NotImplementedException();
        }
    }
}
